package net.acoinWork.dynRender.organize;

import net.acoinWork.dynRender.configParse.ModelBuilder;
import net.acoinWork.dynRender.configParse.propertyModel.Common;
import net.acoinWork.dynRender.configParse.xmlModel.Command;
import net.acoinWork.dynRender.configParse.xmlModel.CommandCol;
import net.acoinWork.dynRender.render.Renderer;
import net.acoinWork.dynRender.render.RendererEngineEnum;
import org.apache.commons.lang.StringUtils;
import org.jdom2.JDOMException;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Map;

/**
 * 组织器,根据传值产生动态资源
 *
 * @author xusk
 * @Date: 15/12/31 下午1:53
 */
public final class Organizer<T> {
    private static final String AC_NAMESPACE = "http://acoinWork.com";
    private final Common common;
    private String libContent;//函数库文件内容
    private Map<String, Command> id2CmdMapping;
    private Renderer<T> renderer;//渲染器
    private volatile Boolean doDebug = false;//debug选项
    private volatile String namespace;//xml命名空间
    private String xmlPath;//xml路径
    private Boolean isLibRequireOnce = false;//是否已第一次引入engine的lib
    private String libPath = "";

    public Organizer(String xmlPath) {
        this(xmlPath, AC_NAMESPACE);
    }

    public Organizer(String xmlPath, String namespace) {
        this.xmlPath = xmlPath;
        this.namespace = namespace;
        common = ModelBuilder.getCommon();
        try {
            CommandCol commandCol = init();
            this.doDebug = common.isDoDebug();
            final RendererEngineEnum rendererEngineEnum = RendererEngineEnum.valueOf(commandCol.getEngine());
            this.renderer = rendererEngineEnum.getEngineRenderer();
            libPath = rendererEngineEnum.getLibPath(common);//函数库文件位置
            StringBuilder sb = fetchLib(libPath);//获取函数库内代码
            libContent = sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据配置文件初始化,完成xml转配置pojo类的变化
     *
     * @return
     * @throws JDOMException
     * @throws IOException
     */
    private CommandCol init() throws JDOMException, IOException {
        ModelBuilder builder = new ModelBuilder(xmlPath, namespace);
        CommandCol commandCol = builder.buildXmlModel();//将配置文件转换为对象
        id2CmdMapping = builder.getId2CmdMapping();//获得sql的id未key,sql对象为value的map
        return commandCol;
    }

    /**
     * 读取lib文件的内容
     *
     * @param libPath
     * @return
     * @throws IOException
     */
    private StringBuilder fetchLib(String libPath) throws IOException {
        StringBuilder sb = new StringBuilder();
        if (StringUtils.isEmpty(libPath)) {
            return sb;
        }
        InputStream is = this.getClass().getResourceAsStream(libPath);
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String lineContent;
        while ((lineContent = br.readLine()) != null) {
            sb.append(lineContent);
        }
        return sb;
    }

    /**
     * 通过id获取sql,不进行渲染器渲染
     *
     * @param id
     * @return
     */
    public String getResById(String id) {
        checkAndLoadFunction();
        //虽然用了锁,也仅仅在doDebug的情况下会执行锁,生产环境不会
        //加锁解决了同一实例的Dao(表示Organizer也是同一实例)在多线程的情况下被调用,
        //由于多线程执行this.init()导致id2SqlMapping内的数据有时会丢失(NullPoint)的问题
        //(这个问题也仅在debug模式下会发生,因为他多次调用this.init())
        if (doDebug) {
            synchronized (doDebug) {
                if (doDebug) {
                    try {
                        init();
                    } catch (JDOMException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return cleanUp(id2CmdMapping.get(id).getPreRender().getContent());
                }
            }
        }
        return cleanUp(id2CmdMapping.get(id).getPreRender().getContent());
    }

    private void checkAndLoadFunction() {
        //改为一个command只要申明了引用函数库,所有sql就都会使用函数库
        //函数库的引入一次生效,不需要每次都引入函数申明
        if (!this.isLibRequireOnce) {
            synchronized (this.isLibRequireOnce) {
                if (!this.isLibRequireOnce) {
                    if (!StringUtils.isBlank(libPath) &&
                            !StringUtils.isBlank(libContent)) {
                        this.renderer.loadLib(libContent);
                        this.isLibRequireOnce = true;
                    }
                }
            }
        }
    }

    /**
     * 通过id获取sql,进行渲染器渲染
     *
     * @param id
     * @param t
     * @return
     */
    public String getResById(String id, T t) {
        checkAndLoadFunction();
        //虽然用了锁,也仅仅在doDebug的情况下会执行锁,生产环境不会
        //加锁解决了同一实例的Dao(表示Organizer也是同一实例)在多线程的情况下被调用,
        //由于多线程执行this.init()导致id2SqlMapping内的数据有时会丢失(NullPoint)的问题
        //(这个问题也仅在debug模式下会发生,因为他多次调用this.init())
        if (doDebug) {
            synchronized (doDebug) {
                if (doDebug) {
                    try {
                        init();
                        return renderSqlByIdAndCtx(id, t);
                    } catch (JDOMException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return renderSqlByIdAndCtx(id, t);
    }

    private String renderSqlByIdAndCtx(String id, T t) {
        StringBuilder renderWord = new StringBuilder();
        final Command command = id2CmdMapping.get(id);
        if (command == null) {
            throw new RuntimeException("this id map no command");
        }
        renderWord.append(command.getPreRender().getContent());
        return cleanUp(renderer.render(renderWord.toString(), t));
    }

    private String cleanUp(String str) {
        return str.replaceAll(" +", " ").trim();
    }

}
